home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
013
/
hdtest.arc
/
HD.ASM
next >
Wrap
Assembly Source File
|
1985-03-05
|
22KB
|
734 lines
page ,132
Title * HD * - Hard Disk Seek and Read, Test Program
Subttl Written, Directed, and Copyrighted by R. Steincross Feb 1985
Comment ~ This program honors several slash commands as shown below to determine the
performance of a hard disk drive or controller. Normally, only the first slash
command encountered is executed. If a "/2" is detected on the command line, then
all testing is directed to the second drive of the controller. If no params are
entered, then the help screen is displayed. (commands listed in search priority)
/2 Use the second drive instead of the first.
/I Information about the drive(s) is reported.
/S n Seek test from 0 to 'n' and Recal from 'n' to 0.
/R n Read 'n' Tracks and report the time taken.
/P Park the drive on the last track.
/? Help screen displayed. (also, if no parameter specified)
~
cr equ 0Dh
lf equ 0Ah
eof equ "$" ;used to terminate strings going to CRT
bell equ 07
DOS equ 21h ;this is the function interrupt into DOS
DISK equ 13h ;this is the interrupt to the Disk BIOS
code_seg segment ;begin code segment
assume cs:code_seg, ds:code_seg
org 080h ;this picks up the command line
cmd_len db ?
cmd_line db ?
org 100h ;COM files org at 100h
HD proc near
entry: jmp go
drive_sel db 80h ;this is drive 1, make 81 for drive 2
db "******" ;this is the ASCII buffer for numeric output,
ascii db "*",eof ;label points to rightmost position in buffer
number dw 0000 ;written to by GET_Num
cs_save dw 0000 ;keep a copy of our Code Segment
leading0 db '0' ;used in Time_Display routine
; The following fields are set when determined by GET_Params:
num_drives db 00 ; 1..D
num_tracks dw 0000 ; 0..T gets altered by get_param
num_heads db 00 ; 0..H-1
num_sectors db 00 ; 1..S
; These storage locations are used by Time_Start, _Stop, & _Display
num_minutes db 00 ; 0..59 minutes
num_seconds db 00 ; 0..59 seconds, used in the get_time routines
num_100ths db 00 ; 0..99, this is 100th of a second
; num_time dw 0000 ; 0..32000, represents 32seconds, in 1/100ths
db 25 dup (' Stack .')
stack dw 1111h
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
org 200h ;this keeps the code in a constant place for testing
go:
mov sp,offset stack
push cs ;This is a Must!
mov ax,cs
mov ds,ax ;all segment registers are AT the Code Segment on start-up
mov es,ax
mov CS_Save,AX ;used when we exit this program
sti ;enable interrupts
mov dx,offset sign_on ;Show the sign-on message
call crt
call upper ;convert command line to all UPPER case
; * * * Second Drive Selection * * *
mov ah,"2"
call find ;see if this is on the command line
jne check_F ;NO, go on to next check
inc drive_sel ;select next drive
mov dx,offset msg_2nd
call crt
check_F: ; * * * FLOPPY Drive Selection * * *
mov ah,"F"
call find
jne check_I
mov al,drive_sel
and al,00000001b ;mask off the high (hard disc) bit
mov drive_sel,al
mov dx,offset msg_floppy
call crt
check_I: ; * * * INFOrmation Report * * *
;we now know whether to use drive 1 or 2, so...
call GET_Params ;get the parameters for selected drive
mov ah,"I"
call find
jne check_S ;go on to next test
call INFO ;report the Facts to user
check_S: ; * * * SEEK 'n' Tracks * * *
mov ah,"S"
call find
jne check_R
call get_num ;get the number of tracks to seek
je chk_s ;number was found, do something
mov dx,offset msg_missing
call crt
jmp short check_R ;abandon the SEEK routine unless param found
chk_s: call SEEK ;do the Seek test
check_R: ; * * * READ 'n' Tracks * * *
mov ah,"R"
call find
jne check_P
call get_num ;get the number of tracks to seek
je chk_r ;number was found, do something
mov dx,offset msg_missing
call crt
jmp short check_P ;abandon the READ routine unless param found
chk_r: call READ ;do the Read test
check_P: ; * * * PARK the Selected Drive * * *
mov ah,"P"
call find
jne check_Q
call PARK
check_Q: ; * * * Look for QUESTION Mark * * *
mov ah,"?"
call find
jne done
call HELP
done:
mov dx,offset msg_blank
call crt ;clear any pending error msgs
mov dx,offset msg_stack ;get ready to squeal of bad stack
mov cx,cs_save
pop ax ;get CS from stack
cmp ax,cx ;better be equal, or else stack is messed up
je exit
call crt
exit: int 20h ;back to DOS
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
;
; END of Top Level Program
;
; MAIN Routines Follow
;
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; * * * INFOrmation Report * * *
INFO:
call show_params
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; * * * Found QUESTION Mark * * *
HELP:
mov dx,offset msg_help
call crt
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; * * * SEEK 'n' Tracks * * *
SEEK:
call RECAL
call Time_Start
mov ax,number
dec ax ;AX has number of tracks, make last tk #
call Load_Cyl
mov dh,num_heads ;restore DH-max head number
mov dl,drive_sel ;DL=drive number, 80 or 81
mov ah,0Ch
int DISK ;seek specified track
jnc seek_1
jmp bad_disk
seek_1:
nop
nop
nop
mov ah,10h ;wait while busy
int DISK
jc seek_1
call Time_Stop
mov dx,offset msg_seektime
call crt
call Time_Display
call Time_Start
call RECAL
call Time_Stop
mov dx,offset msg_recaltime
call crt
call Time_Display
call crlf
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; * * * READ 'n' Tracks * * *
READ:
call RECAL
mov ax,offset Buffer ;find the end of this pgm
shr ax,1 ;put it into SEGMENT format
shr ax,1 ;put it into SEGMENT format
shr ax,1 ;put it into SEGMENT format
shr ax,1 ;put it into SEGMENT format
add ax,cs_save ;find out where we are located
add ax,1000h ;flip up to the next segment
and ax,0F000h ;mask off all but next 64k boundary
mov es,ax ;now we have 64k for DMA from disk
call Time_Start
mov cx,number ;get the number of tracks requested
cmp cx,0000 ;
je read_loop
dec cx ;AX has number of tracks, make last tk #
read_loop:
push cx ;save 'how many times' we want to do this
mov ax,cx
call Load_Cyl ;* * * make sure this sets sector #1
mov dh,00 ;starting head number
mov al,num_heads ;get head number
mul num_sectors ;calculate: AL = HEADS * SECTORS = 55h (85d)
mov dl,drive_sel ;DL=drive number, 80 or 81
mov bx,000 ;we have 64k to DMA into
mov ah,02
int DISK ;read the disc
pop cx ;recover the count of 'tracks to do'
jnc read_1
jmp bad_disk
read_1:
mov ah,10h ;wait while busy
int DISK
jc read_1
loop read_loop ;do it 'till they're all read...
call Time_Stop
mov dx,offset msg_numtracks
call crt
mov ax,number ;get the number of tracks seeked
call ax2dec
call crt
mov dx,offset msg_readtime
call crt
call Time_Display
call crlf
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; * * * PARK the Selected Drive * * *
PARK:
mov dx,offset msg_recal
call crt
call RECAL
mov dx,offset msg_seek
call crt
mov dh,num_heads ;restore DH-max head number
mov dl,drive_sel ;DL=drive number, 80 or 81
mov ax,num_tracks
call Load_Cyl ;put cylinder number into CX from AX
mov ah,0Ch ; SEEK
int DISK
jnc park4
jmp bad_disk
park4:
mov dx,offset msg_parked
call crt
mov ax,num_tracks
call ax2dec ;put on CRT the track we parked at
call crt
call crlf ;... and leave message on CRT
RET
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
;
; END of Main Routines
;
; SUBROUTINES and UTILITIES Follow
;
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; These routines manage the TIMER function.
; They start, stop, and display the elapsed time of some disk action.
Time_Start:
push ax
push bx
push cx
push dx
mov ah,2Ch ;get the time
int DOS ;CH=hrs, CL=min, DH=sec, DL=1/100sec
mov dx,0001 ;clear the seconds, the hundredths,
mov cl,00 ;...and the minutes
mov ah,2Dh ;send time to system
int DOS ;and reset the system timer
pop dx
pop cx
pop bx
pop ax
Ret
Time_Stop:
push ax
push bx
push cx
push dx
mov ah,2Ch ;get the time
int DOS ;CH=hrs, CL=min, DH=sec, DL=1/100sec
mov num_seconds,DH
mov num_100ths,DL
mov num_minutes,CL
; mov ax,100 ; Seconds times 100...
; mul num_seconds ; are now in AX. This is good to 32 sec
; mov dh,00
; add ax,dx ;add in the 100ths of a second for a max of 32000 100ths
; mov num_time,ax ;and save for later use
pop dx
pop cx
pop bx
pop ax
Ret
Time_Display:
push ax
push bx
push cx
push dx
push si
mov al,num_minutes
cmp al,0 ;don't say MINUTES, if there aren't any
je no_mins
call al2dec
call crt
mov dx,offset msg_min
call crt
no_mins:
mov al,num_seconds
call al2dec ;say how many seconds
call crt
mov dx,offset msg_sec
call crt
mov al,num_100ths
call al2dec ;say how many 100ths
cmp al,1 ;were there more than 1 digits?
jg sec_ok ;yes, no problem
dec dx ;point previous char position
mov si,dx
mov al,leading0
mov [si],al ;write the leading zero
sec_ok:
call crt
mov dx,offset msg_100ths
call crt
pop si
pop dx
pop cx
pop bx
pop ax
Ret
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Suck the heads out to track zero on the selected drive
Recal:
mov dl,drive_sel ;80 selects first hard disk, 81 the second
xor cx,cx ; * * * should this be: XOR CX,CX ?
mov ah,11h ;... recal hard disk
int DISK
jnc recal_wait
jmp bad_disk
recal_wait:
nop
nop
nop
mov ah,10h ;wait while busy
int DISK
jc recal_wait
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Load the Cylinder number into the CX registers for seek
; Enter with the binary track number in the AX.
Load_Cyl:
mov ch,al ;put back LSBits
ror ah,1
ror ah,1 ;rotate MSBits into alignment
or ax,0100h ;sector # = 1
mov cl,ah ;put back MSBits
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Convert the AL register to a Decimal number in the ASCII buffer
AL2Dec:
mov ah,00 ;clear hi byte to use AX2DEC routine
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Convert the AX register to a Decimal number and put it in the ASCII buffer.
; AL returns the length of the ASCII number, DX points to the ASCII string.
; The value should probably be less than 32000 for this code to work.
AX2Dec:
push cx
push bx
push si
mov si,offset ascii ;last char position in buffer
mov cl,01 ;ASCII character count
mov bx,10 ;decimal divide
ax2dec_loop:
xor dx,dx ;this is where we put the remainder
div bx ;divide by 10
or dl,30h ;convert to ASCII
mov [si],DL ;save digits from right to left
cmp ax,0000 ;have we anything to divide, still?
je ax_done ;quit if there is nothing left
dec si ;point to previous location in string for next digit
inc cl ;keep track of number of ASCII characters
jmp ax2dec_loop
ax_done: ;leading zeros are supressed
mov dx,si ; keeps from showing leading spaces
mov al,cl ;recover the number of ASCII chars generated
pop si
pop bx
pop cx
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Disk controller got an error, leave existing message on CRT.
; Give BAD message, then controller error number to help debug the problem.
Bad_Disk:
mov dx,offset bad_msg
call crt
mov dl,drive_sel
mov ah,01 ;get controller status
int DISK
; mov al,ah ;error code may be in AL
xor ah,ah
call al2dec
call crt
call crlf
ret
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Get the Parameters of the chosen drive. They are required
; for SHOW_PARAMS, and all routines which seek, park or read
; the drive.
GET_Params:
mov dx,offset msg_notready
call crt ;put up the NOT READY msg in case we aren't
xor cx,cx
mov dh,ch
mov dl,drive_sel ;80 selects first hard disk, 81 the second
mov ah,10h ;check if drive is ready
int DISK
jnc get1 ; is ready, so...
jmp bad_disk
get1:
mov ah,08h ;get drive params
int DISK
jnc get3
jmp bad_disk
get3:
push dx ;clear the error message from the screen
mov dx,offset msg_blank
call crt
pop dx ;restore DH - max head number, and DL - number of drives
mov ah,cl ;get MSBits
rol ah,1 ;only left two bits are used
rol ah,1 ;rotate MSBits into alignment (right 2 bit locations)
and ax,0300h ;mask all but MSBits
mov al,ch ;get LSBits AX now has # of tracks
inc ax ;correct the count to last (diag) track
mov num_tracks,ax ;save number of tracks
mov num_heads,dh ;save number of heads 0..n-1
and cl,00111111b ;mask out hi bits of track 0..t
mov num_sectors,cl ;save number of sectors 1..s (?)
mov num_drives,dl ;save number of drives 1..2
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Put the following parameters on the display:
; Selected drive, Total Number of drives, number of Tracks,
; number of Heads, number of Sectors.
SHOW_Params:
mov dx,offset msg_drives
call crt ;how many drives detected
mov al,num_drives
and al,00000111b
call al2dec ;send AL to CRT in decimal
call crt
mov dx,offset msg_sel
call crt ;which drive was selected
mov al,drive_sel
and al,01h
inc al ; 0 = 1st, 1 = 2nd drive
call al2dec
call crt
mov dx,offset msg_tracks
call crt ;number of tracks this drive
mov ax,num_tracks ; already adjusted for diag cyl
inc ax ;is 0..n-1, s/b 1..n
call ax2dec ;send AX to CRT in decimal
call crt
mov dx,offset msg_heads
call crt ;number of heads this drive
mov al,num_heads
inc al ; 1 = 2hds, 3 = 4hds, 4 = 5hds, etc.
call al2dec
call crt
mov dx,offset msg_sectors
call crt
mov al,num_sectors
call al2dec
call crt
call crlf
ret
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; point the DX to your favorite message, then call here for output on CRT
CRT:
mov ah,09 ; AX gets wasted
int DOS
ret
crt_crlf:
call crt
crlf: mov dx,offset msg_crlf
call crt
ret
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Enter with char in AH to compare against the command line.
; Return with Zero Flag set if found and SI pointing past the char.
; Return Zero Flag clear if not found anywhere on command line.
; All other registers are preserved except SI, CX and flags.
; On return, SI points at char if match was made.
; The command line is found at CS:81, the length of the line
; is at CS:80. Located at length plus 81 is a Carriage Return
; which terminates the line. All chars have been made UPper case.
FIND:
cld ;we want to search forward
mov si,offset cmd_line ;always scan the entire cmd line
mov ch,00
mov cl,[cmd_len] ;get the length
cmp ch,cl
je no_find ;was of zero length, flag no compare
find_slash:
lodsb ;get ax,[si] and increment si
cmp al,"/" ;scan fwd 'till we find one
loopne find_slash ;loop here if not at end of line and not eq /
cmp cl,ch ;are we at zero, yet?
je no_find
lodsb ;get char following slash (/)
cmp ah,al
je return ;this is it! return with
loop find_slash ;try looking at next one
no_find:
cmp ah,0FFh ;set the NO-COMPARE flag
return:
ret
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Gets a number which may follow a slash-letter parameter.
; On entry, SI points past the matched letter in the command line.
; CX should still have count to go to end of line.
; Output is stored in memory at word NUMBER for use by other routines.
; If no number is detected, zero is written into NUMBER and NE=true
; All leading characters except slash (/) are skipped till number is
; found. Then number is converted to binary till non-number is found.
GET_Num:
cmp ch,cl ;bail out if no more chars on cmd line
je no_val
mov dx,0000
mov number,dx ;clear the accumulator
pre_scan: ;throw away all except / till we get a number
mov al,[si] ;get the next char
cmp al,'/' ;bail out if into next command
je no_val
cmp al,'0' ;above an ascii 0 ?
jae get_dig ; yes
cmp al,'9' ;below an ascii 9 ?
jbe get_dig ; yes
inc si ;point to next char
loop pre_scan ;no, look at next char if there are more
jmp short no_val ;end of cmd line, bail out
get_dig:
and ax,000Fh ;convert ASCII to 0 < nibble < 10
mov bx,ax ;save the number for later
mov al,10 ;prepare to multiply
mul number ; AH is zero, AX gets the result ( < 64k )
add ax,bx ;add in previous value
mov number,ax
inc si
mov al,[si]
cmp al,'0'
jb get_done
cmp al,'9'
ja get_done
jmp short get_dig
get_done:
mov al,0
get_out:
cmp al,0
RET
no_val:
mov al,0FFh
jmp get_out
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Convert the entire command line to UPPER case characters
; Registers are trashed as may be convenient.
UPper:
mov si,offset cmd_line ;point to first char of Command line
mov ch,00
mov cl,[cmd_len] ;get the length of the line
cmp ch,cl ;is the length = zero?
je up_done ;yes, bail out
up_shift:
; lodsb ;cmd_line ;can't allow SI to get incremented
mov al,[si] ;get a char
cmp al,'a' ;is it lower case?
jb up_case ;no
cmp al,'z'
ja up_case ;no
and al,0DFh ;yes, make it upper
mov [si],al ;and write it back into place
up_case:
inc si ;point next char
loop up_shift ;decrement the CX counter, and keep on strokin'
up_done:
mov al,'/'
mov [si],al ;mark the end of the string
RET
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Temporary routine to indicate that some Top Level
; programs above have not been completed.
not_done:
mov dx,offset msg_notdone
call crt
Ret
msg_notdone db cr,lf,"The selected function has not yet been implemented.",cr,lf,eof
sign_on db "HD - a Hard Disk Test Program. Copyright 1985 by R. Steincross. Vers 0.2 ",cr,lf,eof
msg_notready db "Disk Drive Not Ready. ",cr,eof
msg_parm db "Couldn't get Drive Parameters. ",cr,eof
msg_blank db " ",cr,eof
msg_2nd db "The SECOND Disc Drive has been selected. ",cr,lf,eof
msg_recal db "Slow Seek to Track Zero. ",cr,eof
msg_seek db "Fast Seeking to Final Track. ",cr,eof
msg_parked db "The drive has been sent to the last (diagnostic) cylinder, track number ",eof
msg_floppy db "The FLOPPY drives have been selected.",cr,lf,eof
msg_stack db "The 8086 stack was not properly cleaned up on exit.",cr,lf,bell,eof
msg_missing db "Missing Parameter. Command Ignored. ",cr,lf,eof
msg_seektime db cr,lf,"Time to seek from zero to desired track: ",eof
msg_recaltime db cr,lf,"Time to slow seek back to track zero: ",eof
msg_min db " minutes, ",eof
msg_sec db ".",eof
msg_100ths db " seconds.",eof
msg_drives db cr,lf,"Hard drives found: ",eof
msg_sel db ", Drive Selected: ",eof
msg_tracks db ", TRACKs: ",eof
msg_heads db ", HEADs: ",eof
msg_sectors db ", SECTORs: ",eof
msg_crlf db cr,lf,eof
msg_numtracks db "Number of tracks read: ",eof
msg_readtime db ". Elapsed time: ",eof
bad_msg db cr,lf,"Either the program or controller failed. Controller Error Code: ",bell,eof
msg_help db cr,lf
db "This program uses the slash commands shown below to determine the ",cr,lf
db "performance of a hard disk drive or controller. If a /2 is detected ",cr,lf
db "on the command line, then all testing is directed to the second drive. ",cr,lf
db "The commands are listed in the order they will be executed. ",cr,lf,lf
db " /2 Use the second drive instead of the first. ",cr,lf
; db " /F The Floppy drive is selected instead of hard disk. ",cr,lf
db " /I INFOrmation about the drive is reported. ",cr,lf
db " /S n SEEK test from 0 to 'n' and Recal from 'n' to 0. ",cr,lf
db " /R n READ 'n' Tracks and report the time taken. ",cr,lf
db " /P PARK the drive on the last track. ",cr,lf
db " /? HELP screen displayed. ",cr,lf,eof
Buffer dw 0 ;this is the start of the read buffer
HD endp
code_seg ends
end entry
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
On entry to the Hard Disk ROM Bios, AH is set with one of the
following values, depending of the function requested:
AH = 00 Reset the hard disk
AH = 08 Get the drive parameters
AH = 0Ch Seek to the desired track
Additional registers which may have to be set on entry are:
DH = 0..7 Head number
DL = 80..87h Drive number where 80 = first hard drive
CH = 0..1023 Low byte of Cylinder number
CL = cc-sssss Sector number and high bits of Cylinder number
AL = 1..80h Number of sectors (or interleave for format)
ES:BX Address of buffer for reads and writes
In the event of a failure, the AH returns the error code, and
the carry bit is set. If no falilure, CY=0 and AH=0.
If Drive Parameters are requested, they are returned as follows:
DH = 0..h-1 Maximum head number
DL = 1..2 Number of hard drives attached
CH = 1..1023 Max useable cylinder number, low byte
CL = cc-sssss Max useable sector number and hi bits of cyl